package org.arrowmania.engine;

import java.util.ArrayList;

import org.arrowmania.engine.beans.Point;

public class GameWorld {
	public GameWorld(int width, int height){
		this.width = width;
		this.height = height;
		
		gameField = new GameObject[width][height];
		
		for(int i=0;i<width;i++){
			for(int j=0;j<height;j++){
				gameField[i][j] = new GameObject(Math.round(3.0f*(float)Math.random()));
			}
		}
	}
	
	//XXX Класс для объекта и цели объекта при исчезновении других элементов
	//Возможно можно придумать что-нибудь получше
	public class RenewalElem{
		public final Point<Integer> p1;
		public final Point<Integer> p2;
		
		RenewalElem(Point<Integer> p1, Point<Integer> p2){
			this.p1 = p1;
			this.p2 = p2;
		}
	}
	
	//XXX Можно сделать один более общий класс для события
	public static class RenewalEvent{
		public ArrayList<RenewalElem> mRenewalsElems;
		
		public RenewalEvent(ArrayList<RenewalElem> renevalElems){
			mRenewalsElems = renevalElems;
		}
	}
	
	//XXX Можно сделать один более общий класс для элемента
	public static class CreateEvent{
		public ArrayList<Point<Integer>> mCreateElems;
		
		public CreateEvent(ArrayList<Point<Integer>> createElems){
			mCreateElems = createElems;
		}
	}
	
	public static interface EventListener{
		public void create(CreateEvent createEvent);
		public void renewal(RenewalEvent renewalEvent);
		public void destroy(DestroyEvent destroyEvent);
	}
	
	public void addEventListener(EventListener eventListener){
		mEventListener = eventListener;
	}
	
	//XXX Можно сделать более общий класс для событий
	public static class DestroyEvent{
		public ArrayList<Point<Integer>> mRemovingElems;
		public DestroyEvent(ArrayList<Point<Integer>> removingElems) {
			mRemovingElems = removingElems;
		}
	}
	
	public GameObject[][] getGameObjects(){
		return gameField;
	}
	
	public void setDataForObject(GameObject object, Object data){
		object.setData(data);
	}
	
	public void setStateForObject(GameObject object, int state){
		object.setState(state);
	}
	
	public int getWidth(){
		return width;
	}
	
	public int getHeight(){
		return height;
	}
	
	//XXX Что делать если i j не в массиве
	public GameObject getObject(int i, int j){
		return gameField[i][j];
	}
	
	public void changeState(int i, int j){
		if((i < width-1) && (j < height -1)){
			if(gameField[i][j].getState()!=-1){
				gameField[i][j].setState((gameField[i][j].getState()+1) % 4);
			}
			if(gameField[i+1][j].getState()!=-1){
				gameField[i+1][j].setState((gameField[i+1][j].getState()+1) % 4);
			}
			if(gameField[i][j+1].getState()!=-1){
				gameField[i][j+1].setState((gameField[i][j+1].getState()+1) % 4);
			}
			if(gameField[i+1][j+1].getState()!=-1){
				gameField[i+1][j+1].setState((gameField[i+1][j+1].getState()+1) % 4);
			}
		}
	}
	
	//XXX этот метод лучше поместить в метод шага
	public void callCreateEvent(){
		CreateEvent event = new CreateEvent(getCreateElems());
		mEventListener.create(event);
	}
	
	protected ArrayList<Point<Integer>> getCreateElems(){
		ArrayList<Point<Integer>> createElems = new ArrayList<Point<Integer>>();
		
		for(int i=0;i<width;i++){
			for(int j=0;j<height;j++){
				if(gameField[i][j].getState() == -1){
					createElems.add(Point.make(i, j));
				}
			}
		}
		
		return createElems;
	}
	
	//XXX этот метод лучше поместить в метод шага
	public void callRenewalEvent(){
		RenewalEvent event = new RenewalEvent(getRenewalElems());
		mEventListener.renewal(event);
	}
	
	protected ArrayList<RenewalElem> getRenewalElems(){
		ArrayList<RenewalElem> renewalElems = new ArrayList<RenewalElem>();
		
		for(int i=0;i<width;i++){
			int offset = 0;
			for(int j=0;j<height;j++){
				if(gameField[i][j].getState() == -1){
					offset++;
					continue;
				}
				
				if(offset != 0){
					renewalElems.add(new RenewalElem(Point.make(i, j), Point.make(i, j-offset)));
				}
			}
		}
		
		return renewalElems;
	}
	
	protected void omitElemsInColumn(int i){
		int offset = 0;
		
		for(int j=0;j<height - offset;j++){
			if(gameField[i][j].getState() == -1){
				for(int k=j;k<height-1;k++){
					gameField[i][k].setState(gameField[i][k+1].getState());
					gameField[i][k].setData(gameField[i][k+1].getData());
				}
				
				gameField[i][height-1].setState(-1);
				gameField[i][height-1].setData(null);
				
				offset++;
				j--;
			}
		}
	}
	
	public void omitElems(){
		for(int i=0;i<width;i++){
			omitElemsInColumn(i);
		}
	}
	
	public boolean isIdleRun(){
		return isIdleRun;
	}
	
	//XXX этот метод лучше поместить в метод шага
	public void callDestroyEvent(){
		DestroyEvent event = new DestroyEvent(getRemovingElems());
		
		if(event.mRemovingElems.size() != 0){
			isIdleRun = true;
		} else {
			isIdleRun = false;
		}
		
		mEventListener.destroy(event);
	}
	
	protected ArrayList<Point<Integer>> getRemovingElems(){
		ArrayList<Point<Integer>> removingElems = new ArrayList<Point<Integer>>();
		mergeLists(removingElems,getHorizontalRemovingElems());
		mergeLists(removingElems,getVerticalRemovingElems());
		return removingElems;
	}
	
	protected void mergeLists(ArrayList<Point<Integer>> List1, ArrayList<Point<Integer>>List2){
		for(Point<Integer> i:List2){
			if(!contains(List1,i)){
				List1.add(i);
			}
		}
	}
	
	protected boolean contains(ArrayList<Point<Integer>> List,Point<Integer> testing){
		for(Point<Integer> i: List){
			if(i.x == testing.x && i.y == testing.y)
				return true;
		}
		return false;
	}
	
	//XXX Метод очень похож на метод getVerticalRemovingElems.
	//Нужно попробовать объединить в один
	protected ArrayList<Point<Integer>> getHorizontalRemovingElems(){
		ArrayList<Point<Integer>> hRemovingElems = new ArrayList<Point<Integer>>();
		for(int i=0;i<width;++i){
			for(int j=1;j<height-1;++j){
				if((gameField[i][j].getState()==gameField[i][j-1].getState()) && (gameField[i][j].getState()==gameField[i][j+1].getState())){
					hRemovingElems.add(Point.make(i,j));
					hRemovingElems.add(Point.make(i,j-1));
					hRemovingElems.add(Point.make(i,j+1));
				}
			}
		}
		return hRemovingElems;
	}
	
	//XXX Метод очень похож на метод getHorizontalRemovingElems.
	//Нужно попробовать объединить в один
	protected ArrayList<Point<Integer>> getVerticalRemovingElems(){
		ArrayList<Point<Integer>> vRemovingElems = new ArrayList<Point<Integer>>();
		for(int j=0;j<width;++j){
			for(int i=1;i<height-1;++i){
				if(gameField[i][j].getState()==gameField[i-1][j].getState() && gameField[i][j].getState()==gameField[i+1][j].getState()){
					vRemovingElems.add(Point.make(i,j));
					vRemovingElems.add(Point.make(i-1,j));
					vRemovingElems.add(Point.make(i+1,j));
				}
			}
		}
		return vRemovingElems;
	}
	
	private GameObject[][] gameField;
	
	private int width;
	private int height;
	
	private EventListener mEventListener;
	
	private boolean isIdleRun = false;
}
